﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace Green.AgileMapper
{
    public class XMLMappingConfigurator : MappingConfiguratorBase
    {
        private static Dictionary<string, List<XmlConfigMappingRuleValue>> dict = new Dictionary<string, List<XmlConfigMappingRuleValue>>();
        private static Dictionary<string, Type> ruleType = new Dictionary<string, Type>();
        private static readonly object lockObj = new object();
        private static readonly object lockTypeObj = new object();
        public string XmlFile
        {
            get;
            private set;
        }
        public XMLMappingConfigurator(string xmlfile)
        {
            //if (!System.IO.File.Exists(System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, xmlfile)))
            //{
            //    throw new System.IO.FileNotFoundException(xmlfile);
            //}
            this.XmlFile = xmlfile;
            LoadConfig(xmlfile);
        }

        protected virtual void LoadConfig(string xmlfile)
        {
            if (!dict.ContainsKey(xmlfile))
            {
                lock (lockObj)
                {
                    if (!dict.ContainsKey(xmlfile))
                    {
                        var xdoc = XDocument.Load(xmlfile);
                        ReadFromXmlConfig(xdoc);
                    }
                }
            }
        }

        protected virtual void ReadFromXmlConfig(XDocument xdoc)
        {
            LoadExtensionElement(xdoc);
            LoadMappingRuleElement(xdoc);

        }

        private void LoadMappingRuleElement(XDocument xdoc)
        {
            var mappingCollection = xdoc.Root.Element("Mappings").Elements("Mapping").ToList();
            List<XmlConfigMappingRuleValue> configrules = new List<XmlConfigMappingRuleValue>();
            if (mappingCollection != null && mappingCollection.Count > 0)
            {
                mappingCollection.ForEach(map =>
                {
                    XmlConfigMappingRuleValue xmlConfigMappingRuleValue = new XmlConfigMappingRuleValue();
                    xmlConfigMappingRuleValue.FromType = Type.GetType(map.Attribute("FromType").Value);
                    List<IMappingRule> rules = new List<IMappingRule>();
                    map.Elements().ToList().ForEach(el =>
                    {
                        var ruleName = el.Name.LocalName;
                        if (ruleType.ContainsKey(ruleName))
                        {
                            var elRuleType = ruleType[ruleName];
                            var rule = (IMappingRule)Activator.CreateInstance(elRuleType);
                            if (rule != null)
                            {
                                var attributesDict = el.Attributes().ToDictionary(t => t.Name.LocalName, t => (object)t.Value);
                                attributesDict.Add("DefaultEqualExpression", this.DefaultEqualExpression);
                                rule.LoadXmlConfig(attributesDict);
                                rules.Add(rule);
                            }
                        }
                       
                    });
                    var ignoreCollection = map.Element("Ignores").Elements("Ignore").Select(t => t.Attribute("Name").Value)
                           .Concat(rules.Select(t => t.FromPoperty)).ToList();
                    var nonIgnoreMapping = xmlConfigMappingRuleValue.FromType.GetProperties().Select(t => t.Name).Except(ignoreCollection);
                    nonIgnoreMapping.ToList().ForEach(p =>
                    {
                        rules.Add(new SimpleMappingRule(p, p));
                    });
                    xmlConfigMappingRuleValue.Rules = rules.ToArray();
                    configrules.Add(xmlConfigMappingRuleValue);                  

                });
                dict.Add(this.XmlFile, configrules);
            }
        }

        private void LoadExtensionElement(XDocument xdoc)
        {
            var extensionCollection = xdoc.Root.Element("Extensions").Elements("Extension").ToList();
            if (extensionCollection != null && extensionCollection.Count > 0)
            {
                extensionCollection.ForEach(e =>
                {
                    var name = e.Attribute("Name").Value;
                    if (!ruleType.ContainsKey(name))
                    {
                        lock (lockTypeObj)
                        {
                            if (!ruleType.ContainsKey(name))
                            {
                                var type = Type.GetType(e.Attribute("Type").Value);
                                if (type == null)
                                {
                                    throw new Exception(string.Format("{0} 不存在！",e.Attribute("Type").Value));
                                }
                                ruleType.Add(name, type);
                            }
                        }
                    }
                });
            }
        }
        #region IMappingConfigurator 成员

        public override bool IsNeedCache
        {
            get { return false; }
        }

        public override IMappingRule[] GetMappingRule(object from)
        {
            if (dict.ContainsKey(this.XmlFile))
            {
                var xmlConfigMappingRuleValue = dict[this.XmlFile].FirstOrDefault(t => t.FromType == from.GetType());
                if (xmlConfigMappingRuleValue != null)
                {
                    return xmlConfigMappingRuleValue.Rules;
                }
            }
            return new IMappingRule[0];
        }

        public override string GetConfigurationName()
        {
            return "XMLMapping";
        }

        #endregion
    }

    internal class XmlConfigMappingRuleValue
    {
        public Type FromType
        { get; set; }

        public IMappingRule[] Rules
        {
            get;
            set;
        }
    }
}
